package com.ruoyi.worker.service.workerSys.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.worker.constant.Constant;
import com.ruoyi.worker.domain.TermWorker;
import com.ruoyi.worker.domain.workAttendance.RzScheduleStaffPlan;
import com.ruoyi.worker.domain.workAttendance.RzScheduleTask;
import com.ruoyi.worker.domain.workAttendance.dto.AttendanceShift;
import com.ruoyi.worker.domain.workAttendance.dto.SchedulePlanDTO;
import com.ruoyi.worker.domain.workAttendance.vo.PlanDayVO;
import com.ruoyi.worker.domain.workAttendance.vo.SchedulePlanVO;
import com.ruoyi.worker.mapper.TermAttendanceGroupMapper;
import com.ruoyi.worker.mapper.TermAttendanceGroupV2Mapper;
import com.ruoyi.worker.mapper.TermWorkerMapper;
import com.ruoyi.worker.mapper.attendance.RzScheduleStaffPlanMapper;
import com.ruoyi.worker.mapper.attendance.RzScheduleTaskMapper;
import com.ruoyi.worker.service.workerSys.ISchedulingTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 班组排班计划实现类
 *
 * @author Hush
 */
@Service
@Slf4j
public class SchedulingTaskImpl implements ISchedulingTask {
    private static final int FIRST_ROW = 4;
    private static final int FIRST_COL = 9;
    private static final String HIDDEN_SHEET_NAME = "考勤班次";
    private List<TermWorker> cashTermWorkerList;
    @Autowired
    private RzScheduleTaskMapper scheduleTaskMapper;
    @Resource
    private RzScheduleStaffPlanMapper schedulePlanMapper;
    @Autowired
    private TermAttendanceGroupMapper termAttendanceGroupMapper;
    @Autowired
    private TermWorkerMapper termWorkerMapper;

    @Resource
    private TermAttendanceGroupV2Mapper groupV2Mapper;

    // 这个Map是用于检查员工排班情况的
    private Map<String, Object> staffScheduleMap;
    // 等待修改的未生效的集合
    private List<RzScheduleStaffPlan> waitForUpdateList;
    // 一个标识
    private Boolean flag;


    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 保存任务
     *
     * @param task: task对象
     * @author Hush.
     * @since 2021/11/19 13:35
     */
    @Override
    public void saveTask(RzScheduleTask task) {
        scheduleTaskMapper.insertRzScheduleTask(task);
    }

    /**
     * 更新任务
     *
     * @param task: task对象
     * @author Hush.
     * @since 2021/11/19 13:35
     */
    @Override
    public void updateTask(RzScheduleTask task) {
        scheduleTaskMapper.updateRzScheduleTask(task);
    }

    /**
     * 删除任务
     *
     * @param ids: id主键
     * @author Hush.
     * @since 2021/11/19 13:34
     */
    @Override
    public void delTaskByIds(Long[] ids) {
        Date nowDate = DateUtils.getNowDate();
        List<String> result = new ArrayList<>();
        for (int i = 0; i < ids.length; i++) {
            Long id = ids[i];
            RzScheduleTask task = scheduleTaskMapper.selectRzScheduleTaskById(id);
            if (nowDate.after(task.getScheduleStartTime())) {
                result.add("第" + (i + 1) + "项");
            }
        }
        if (CollectionUtil.isNotEmpty(result)) {
            throw new CustomException(StringUtils.join(result, ",") + "其开始时间已超过当前时间,不可删除.", HttpStatus.ERROR);
        }
        scheduleTaskMapper.deleteRzScheduleTaskByIds(ids);
        schedulePlanMapper.deleteRzScheduleTaskByTaskIds(ids);
    }

    /**
     * 查询任务集合
     *
     * @param task: task对象
     * @Return: java.util.List<com.ruoyi.worker.domain.workAttendance.RzScheduleTask>
     * @author Hush.
     * @date: 2021/11/19 9:50
     */
    @Override
    public List<RzScheduleTask> listTask(RzScheduleTask task) {
        return scheduleTaskMapper.selectRzScheduleTaskList(task);
    }

    @Override
    public List<AttendanceShift> listAttendanceShift() {
        return termAttendanceGroupMapper.listAttendanceShift();
    }

    private List<AttendanceShift> listAttendanceShiftV2() {
        return groupV2Mapper.listAttendanceShiftV2();
    }

    /**
     * 生成排班计划模板
     *
     * @param task: 任务对象
     * @return com.ruoyi.worker.domain.workAttendance.dto.SchedulePlanDTO
     * @author Hush.
     * @since 2021/11/19 13:36
     */
    @Override
    public SchedulePlanDTO generateSchedulingPlanPreviewTemplateData(RzScheduleTask task) throws ParseException {
        //获取考勤通用班次信息
        // V1.2 版本新增离职、请假、工伤三个选项
//        String[] dataArray = this.getDataArray();
        String[] dataArray = this.getDataArrayV2();
        Map<String, Object> value = Maps.newHashMap();
        value.put("startDate", task.getScheduleStartTime());
        value.put("endDate", task.getScheduleEndTime());
        value.put("teamName", task.getDeptName());
        value.put("roleList", StringUtils.join(dataArray, " "));
        //班组人员信息 行数据
        List<Map<String, Object>> rows = scheduleTaskMapper.selectTermWorker(task.getDeptId());
        for (Map<String, Object> row : rows) {
            row.put("dept_id", task.getDeptId());
            row.put("dept_name", task.getDeptName());
        }
        value.put("valList", rows);
        //排班日期 列数据
        List<Date> dates = DateUtils.findDateList(DateUtils.dateTime(task.getScheduleStartTime()), DateUtils.dateTime(task.getScheduleEndTime()));
        List<Map<String, Object>> colList = new ArrayList<>();
        for (Date m : dates) {
            Map<String, Object> map = Maps.newHashMap();
            map.put("date", m);
            colList.add(map);
        }
        value.put("colList", colList);
        JSONObject model = new JSONObject();
        model.put(Constant.FIRST_ROW, FIRST_ROW);
        model.put(Constant.FIRST_COL, FIRST_COL);
        model.put(Constant.END_ROW, FIRST_ROW + rows.size() - 1);
        model.put(Constant.END_COL, FIRST_COL + colList.size() - 1);
        model.put(Constant.DATA_ARRAY, dataArray);
        model.put(Constant.HIDDEN_SHEET_NAME, HIDDEN_SHEET_NAME);
        model.put(Constant.ROWS_SIZE, rows.size());
        String fileName = String.format("%s计划排班表(%s~%s)",
                task.getDeptName(),
                DateUtils.dateTime(task.getScheduleStartTime()),
                DateUtils.dateTime(task.getScheduleEndTime()));
        model.put(Constant.CODED_FILE_NAME, fileName);
        return new SchedulePlanDTO().setModel(model).setValue(value);
    }

    /**
     * 下拉框数组
     *
     * @return java.lang.String[]
     * @author Hush.
     * @since 2021/11/18 21:47
     **/
    @Deprecated
    private String[] getDataArray() {
        List<String> list = this.listAttendanceShift().stream().map(AttendanceShift::getValue).collect(Collectors.toList());
        String[] result = new String[list.size()];
        list.toArray(result);
        return result;
    }

    /**
     * @author: 陈宇寰
     * @date: 2021-12-23 17:06:15
     * @params:
     * @return:
     * @description: v1.2 excel下拉框新增离职、工伤、请假三个状态
     */
    private String[] getDataArrayV2() {
        List<String> list = this.listAttendanceShiftV2().stream().map(AttendanceShift::getValue).collect(Collectors.toList());
        String[] result = new String[list.size()];
        list.toArray(result);
        return result;
    }

    /**
     * 处理员工的排班计划
     *
     * @param list : 计划排班数据
     * @param task : 主表任务
     * @author Hush.
     * @since 2021/11/19 18:14
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void dealSchedulingPlanData(List<Map<String, Object>> list, RzScheduleTask task) {
        if (CollectionUtil.isEmpty(list)) {
            throw new CustomException("错误:考勤人员为空", 500);
        }
        this.staffScheduleMap = new HashMap<>();
        this.waitForUpdateList = new ArrayList<>();
        this.flag = false;
        //保存task主表数据,返回taskId
        Map<String, Object> map = list.get(0);
        List<Map<String, Object>> plan = (List<Map<String, Object>>) map.get(Constant.PLAN_KEY);
        Map<String, Object> startMap = plan.get(0);
        Date startDay = (Date) startMap.get(Constant.PLAN_KEY_DATE);
        Map<String, Object> endMap = plan.get(plan.size() - 1);
        Date endDay = (Date) endMap.get(Constant.PLAN_KEY_DATE);
        task.setDeptId(Long.parseLong((String) map.get("deptId"))).setDeptName((String) map.get("deptName"))
                .setScheduleStartTime(startDay).setScheduleEndTime(endDay);
        if (scheduleTaskMapper.taskIsExists(startDay, endDay, task.getDeptId()) == 0) {
            scheduleTaskMapper.insertRzScheduleTask(task);
        }
        //创建每日的员工排班计划实体并保存
        List<RzScheduleStaffPlan> plans = this.excelDataToStaffPlanEntity(list, task);

        if (CollectionUtil.isEmpty(plans) && CollectionUtil.isEmpty(waitForUpdateList)) {
            if (this.flag) {
                throw new CustomException("员工的排班都已生效，无法修改", 500);
            }
            throw new CustomException("请填写每位员工的计划排班数据", 500);
        }
        if (!CollectionUtil.isEmpty(plans)) {
            // 新增员工排班表
            for (List<RzScheduleStaffPlan> p : averageAssign(plans, 3)) {
                schedulePlanMapper.insertBatchRzScheduleStaffPlan(p);
            }
        }
        // 修改还未生效的排班表
        if (this.waitForUpdateList.size() > 0) {
            schedulePlanMapper.batchUpdatePlan(this.waitForUpdateList);
        }

    }

    private static <T> List<List<T>> averageAssign(List<T> source, int n) {
        List<List<T>> result = new ArrayList<List<T>>();
        int remainder = source.size() % n;  //(先计算出余数)
        int number = source.size() / n;  //然后是商
        int offset = 0;//偏移量
        for (int i = 0; i < n; i++) {
            List<T> value = null;
            if (remainder > 0) {
                value = source.subList(i * number + offset, (i + 1) * number + offset + 1);
                remainder--;
                offset++;
            } else {
                value = source.subList(i * number + offset, (i + 1) * number + offset);
            }
            result.add(value);
        }
        return result;
    }

    /**
     * 获取排班详情
     *
     * @param searchDto: 检索条件
     * @return java.util.List<SchedulePlanVO>
     * @author Hush.
     * @since 2021/11/19 20:03
     */
    @Override
    public List<SchedulePlanVO> getDetailSchedulingPlan(RzScheduleStaffPlan searchDto) throws ParseException {
        RzScheduleTask task = scheduleTaskMapper.selectRzScheduleTaskById(searchDto.getScheduleTaskId());
        //排班日期
        List<Date> dates = DateUtils.findDateList(DateUtils.dateTime(task.getScheduleStartTime()), DateUtils.dateTime(task.getScheduleEndTime()));
        //查询该任务下员工排班计划
        long start = System.nanoTime();
        List<SchedulePlanVO> list = schedulePlanMapper.selectStaffPlanGrouping(searchDto);
        System.out.println("执行时间：" + (System.nanoTime() - start));
        for (SchedulePlanVO planVO : list) {
            List<PlanDayVO> days = planVO.getPlanDays();
            List<PlanDayVO> tempList = dates.stream().map(m -> new PlanDayVO().setScheduleDate(m)).collect(Collectors.toList());
            planVO.setPlanDays(verifyPlanDay(tempList, days));
        }
        return list;
    }

    /**
     * 校验排班计划
     *
     * @param: tempList 总得日期天数
     * @param: days 数据库中的排班数据
     * @return: java.util.List<com.ruoyi.worker.domain.workAttendance.vo.PlanDayVO>
     * @author Hush.
     * @date: 2021/11/21 22:18
     */
    private List<PlanDayVO> verifyPlanDay(List<PlanDayVO> tempList, List<PlanDayVO> days) {
        for (PlanDayVO vo : tempList) {
            for (PlanDayVO day : days) {
                if (DateUtil.isSameDay(vo.getScheduleDate(), day.getScheduleDate())) {
                    vo.setAttendanceGroupName(day.getAttendanceGroupName());
                    break;
                }
            }
        }
        return tempList;
    }

    /**
     * 获取考勤规则Map
     *
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author Hush.
     * @since 2021/11/19 20:09
     */
    private Map<String, Object> cacheAttendanceRule() {
        Map<String, Object> map = Maps.newHashMap();
        for (AttendanceShift rule : this.listAttendanceShiftV2()) {
            map.put(rule.getValue(), rule.getKey());
        }
        return map;
    }

    /**
     * 校验该天有无工作类型的排班计划
     *
     * @param dto : 检索条件
     * @author Hush.
     * @since 2021/11/21 13:14
     */
    private void checkStaffPlanDay(RzScheduleStaffPlan dto) {
        RzScheduleStaffPlan plan = schedulePlanMapper.selectStaffPlan(dto);
        if (!ObjectUtil.isEmpty(plan)) {
            //员工(340191199910118978)在2021年11月21日已有工作排班
            String mess =
                    plan.getStaffName() + "(" + plan.getIdCard() + ")" + "在" + DateUtils.dateTime(plan.getScheduleDate()) + "已有工作排班";
            throw new CustomException(mess, 500);
        }
    }

    /**
     * @author: 陈宇寰
     * @date: 2021-12-24 12:54:43
     * @params:
     * @return:
     * @description: v1.2 版本修改的检查算法
     * 逻辑为从取出的指定月份中查找，如能找到说明信息是存在的，然后和当前时间对比，如果当前时间早于修改时间，则允许修改
     */
    private boolean checkStaffPlanDayV2(RzScheduleStaffPlan dto) {
        String key = dto.getIdCard() + ":" + sdf.format(dto.getScheduleDate());
        Long value = (Long) this.staffScheduleMap.get(key);
        if (!ObjectUtil.isNull(value)) {
            if (new Date().compareTo(dto.getScheduleDate()) < 0 && !dto.getAttendanceGroupId().equals(value)) {
                // 需要日期晚于进入并且排班状态有改变的才进行更改
                waitForUpdateList.add(dto);
            }
            this.flag = true;
            return true;
        }
        return false;
    }

    private void buildStaffPlanCheckList(Date startDate, Date endDate) {

        List<RzScheduleStaffPlan> plans = schedulePlanMapper.staffPlanOnMonth(startDate, endDate);

        plans.forEach(item -> {
            this.staffScheduleMap.put(item.getIdCard() + ":" + sdf.format(item.getScheduleDate()), item.getAttendanceGroupId());
        });
    }


    /**
     * 校验该天是否离职
     *
     * @param dto : 检索条件
     * @author Hush.
     * @since 2021/11/21 13:14
     */
    private void checkStaffPlanLeaveTime(RzScheduleStaffPlan dto) {
        TermWorker workerInfo = this.cashTermWorkerList.stream().filter(con -> dto.getStaffId().equals(con.getId())).findFirst().get();
        //判断离职时间是否在在当前排班计划中
        if (null != workerInfo.getLeaveTime() && workerInfo.getLeaveTime().before(dto.getScheduleDate()) && StringUtils.isNotEmpty(dto.getAttendanceGroupName())) {
            //员工xxx(304123199910100989)的离职日期为xxxx-xx-xx,请将该人员xxxx-xx-xx之后的排班计划清除后再试
            String mess = String.format("员工%s(%s)的离职日期为%s,请将该人员%s之后的排班计划清除后再试",
                    workerInfo.getWorkerName(), workerInfo.getIdCard(),
                    DateUtils.dateTime(workerInfo.getLeaveTime()),
                    DateUtils.dateTime(workerInfo.getLeaveTime()));
            throw new CustomException(mess, 500);
        }
    }

    /**
     * 校验人员信息
     *
     * @param dto: 检索条件
     * @author Hush.
     * @since 2021/11/21 14:07
     */
    private void checkStaffInfo(RzScheduleStaffPlan dto) {
        //检查该班组中是否有对应的人员信息
        boolean worker = this.cashTermWorkerList.stream().anyMatch(con -> {
            boolean idB = con.getId().equals(dto.getStaffId());
            boolean idCardB = con.getIdCard().equals(dto.getIdCard());
            boolean workerNameB = con.getWorkerName().equals(dto.getStaffName());
            return idCardB && workerNameB && idB;
        });
        if (!worker) {
            //员工(340191199910118978)系统未检测到员工数据
            String mess =
                    dto.getStaffName() + "(" + dto.getIdCard() + ")" + "-系统未检测到该班组中的员工数据";
            throw new CustomException(mess, 500);
        }
    }

    /**
     * 将同一般组的人员信息放入缓存
     *
     * @param deptId: 班组id
     * @author Hush.
     * @since 2021/11/26 11:06
     */
    private void putTermWorkerToCash(Long deptId) {

        this.cashTermWorkerList = schedulePlanMapper.selectStaffInTheSchedulingModule(new RzScheduleStaffPlan().setDeptId(deptId));
    }

    /**
     * 覆盖原排班计划
     *
     * @param list:新排班计划数据
     * @param dto:原考勤主任务
     * @author Hush.
     * @since 2021/11/21 14:50
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void coverSchedulingPlanData(List<Map<String, Object>> list, RzScheduleTask dto) throws ParseException {

        if (CollectionUtil.isEmpty(list)) {
            throw new CustomException("错误:考勤人员为空", 500);
        }
        //导入校验时间范围
        RzScheduleTask temp = scheduleTaskMapper.selectRzScheduleTaskById(dto.getId());
        List<String> dates = DateUtils.findDates(DateUtils.dateTime(temp.getScheduleStartTime()), DateUtils.dateTime(temp.getScheduleEndTime()));
        Map<String, Object> map = list.get(0);
        List<Map<String, Object>> plan = (List<Map<String, Object>>) map.get(Constant.PLAN_KEY);
        Map<String, Object> startMap = plan.get(0);
        Date startDay = (Date) startMap.get(Constant.PLAN_KEY_DATE);
        Map<String, Object> endMap = plan.get(plan.size() - 1);
        Date endDay = (Date) endMap.get(Constant.PLAN_KEY_DATE);
        //校验开始,结束日期
        boolean sameStartDay = DateUtils.isSameDay(startDay, temp.getScheduleStartTime());
        boolean sameEndDay = DateUtils.isSameDay(endDay, temp.getScheduleEndTime());
        if (plan.size() != dates.size() || !sameStartDay || !sameEndDay) {
            throw new CustomException("覆盖错误:日期校验存在问题!请检查文件的排班计划时间列", 500);
        }
        //更新主表任务
        scheduleTaskMapper.updateRzScheduleTask(dto);
        //删除当天之后的排班数据
        Date timeOfTheDay = DateUtils.getTheLastTimeOfTheDay(null);
        RzScheduleStaffPlan planDto = new RzScheduleStaffPlan();
        planDto.setScheduleDate(timeOfTheDay).setScheduleTaskId(dto.getId());
        schedulePlanMapper.deleteRzScheduleStaffPlan(planDto);
        //excel数据转化为实体对象
        List<RzScheduleStaffPlan> plans = this.excelDataToStaffPlanEntity(list, temp);
        //仅保存当天之后的排班数据
        List<RzScheduleStaffPlan> filterPlan = plans.stream().filter(
                one -> one.getScheduleDate().after(timeOfTheDay)
        ).collect(Collectors.toList());

        if (CollectionUtil.isNotEmpty(filterPlan)) {
            schedulePlanMapper.insertBatchRzScheduleStaffPlan(filterPlan);
        }

    }

    private List<RzScheduleStaffPlan> excelDataToStaffPlanEntity(List<Map<String, Object>> list, RzScheduleTask task) {
        //获取班组人员信息
        this.putTermWorkerToCash(task.getDeptId());
        //获取考勤规则Map,用于匹配考勤规则ID
        Map<String, Object> cash = cacheAttendanceRule();
        List<RzScheduleStaffPlan> plans = new ArrayList<>();
        // 准备检验数据
        this.buildStaffPlanCheckList(task.getScheduleStartTime(), task.getScheduleEndTime());
        for (Map<String, Object> staff : list) {
            String string = JSONObject.toJSONString(staff);
            RzScheduleStaffPlan scheduleStaffPlan = JSONObject.parseObject(string, RzScheduleStaffPlan.class);
            scheduleStaffPlan.setScheduleTaskId(task.getId());
            //校验人员信息
            this.checkStaffInfo(scheduleStaffPlan);
            List<Map<String, Object>> maps = (List<Map<String, Object>>) staff.get(Constant.PLAN_KEY);

            for (Map<String, Object> temp : maps) {
                RzScheduleStaffPlan entity = new RzScheduleStaffPlan();
                BeanUtil.copyProperties(scheduleStaffPlan, entity);
                Date date = (Date) temp.get(Constant.PLAN_KEY_DATE);
                String attendName = (String) temp.get(Constant.PLAN_KEY_PLAN);
                if (StringUtils.isEmpty(attendName)) {
                    continue;
                }
                //考勤规则校验
                Long groupId = (Long) cash.get(attendName);
                if (ObjectUtil.isEmpty(groupId)) {
                    throw new CustomException("考勤规则异常:" + attendName + "不存在", 500);
                }
                entity.setHandleTime(new Date()).setScheduleDate(date).setAttendanceGroupName(attendName).setAttendanceGroupId(groupId);
                //校验该天有无工作类型的排班计划
                RzScheduleStaffPlan dto = new RzScheduleStaffPlan();
                dto.setScheduleDate(entity.getScheduleDate())
                        .setStaffId(entity.getStaffId())
                        .setAttendanceGroupId(entity.getAttendanceGroupId())
                        .setAttendanceGroupName(attendName)
                        .setDeptId(entity.getDeptId())
                        .setIdCard(entity.getIdCard())
                        .setScheduleTaskId(entity.getScheduleTaskId());
//                this.checkStaffPlanDay(dto); // <- 这个平均耗时2.7秒 / 次
                if (this.checkStaffPlanDayV2(dto)) continue;
//                //检测员工离职时间
                this.checkStaffPlanLeaveTime(dto);
                plans.add(entity);
            }
        }

        return plans;
    }

    /**
     * 校验排班任务模板有无日期冲突
     *
     * @param dto : 检索条件
     * @return java.util.List<com.ruoyi.worker.domain.workAttendance.RzScheduleTask>
     * @author Hush.
     * @since 2021/11/22 11:01
     */
    @Override
    public List<RzScheduleTask> checkTaskShiftDate(RzScheduleTask dto) {
        return scheduleTaskMapper.checkTaskShiftDate(dto);
    }

    /**
     * 处理离职员工排班
     *
     * @param plan :删除条件
     * @return int 删除数量
     * @author Hush.
     * @since 2021/12/21 14:04
     */
    @Override
    public int handleTheSchedulingOfDepartingEmployees(RzScheduleStaffPlan plan) {
        //删除离职员工未来的排班数据
        return schedulePlanMapper.deleteRzScheduleStaffPlan(plan);
    }

    public List<TermWorker> getCashTermWorkerList() {
        return cashTermWorkerList;
    }

    public void setCashTermWorkerList(List<TermWorker> cashTermWorkerList) {
        this.cashTermWorkerList = cashTermWorkerList;
    }
}
